<template>
  <div
    ref="container"
    class="virtual-list-container"
    @scroll="onScroll"
    @wheel="onWheel"
    @touchstart="onTouchStart"
    @touchmove="onTouchMove"
  >
    <div class="virtual-list-phantom" :style="{ height: totalHeight + 'px' }"></div>
    <div
      class="virtual-list-content"
      ref="virtualListContentRef"
      :style="{ transform: `translateY(${offsetY}px)` }"
    >
      <div
        v-for="item in visibleData"
        :key="JSON.stringify(item)"
        class="virtual-list-item"
        :ref="(el) => setItemRef(el, item.index)"
      >
        <slot :item="item.data" :index="item.index"></slot>
      </div>
    </div>

    <transition name="fade-scale">
      <div v-show="showBackToTop" class="back-to-top-btn" @click="scrollToTop">
        <van-icon name="arrow-up" />
      </div>
    </transition>
  </div>
</template>

<script>
export default {
  name: "VirtualList",
  props: {
    // 数据列表
    data: {
      type: Array,
      default: () => [],
    },
    // 容器高度
    // containerHeight: {
    //   type: Number,
    //   default: 400,
    // },
    // 每项高度（预估高度）
    itemHeight: {
      type: Number,
      default: 60,
    },
    // 缓冲区大小（上下各渲染多少项）
    bufferSize: {
      type: Number,
      default: 5,
    },
  },
  data() {
    return {
      scrollTop: 0,
      itemHeights: new Map(), // 存储每个item的实际高度
      observer: null,
      isScrolling: false,
      scrollTimer: null,
      touchStartY: 0,
      touchStartScrollTop: 0,
      containerHeight: 400,
    };
  },
  computed: {
    // 总高度
    totalHeight() {
      let total = 0;
      for (let i = 0; i < this.data.length; i++) {
        total += this.getItemHeight(i);
      }
      return total;
    },
    // 可见区域开始索引
    visibleStartIndex() {
      return this.getIndexByOffset(this.scrollTop);
    },
    // 可见区域结束索引
    visibleEndIndex() {
      const visibleCount = Math.ceil(this.containerHeight / this.itemHeight);
      return Math.min(this.visibleStartIndex + visibleCount, this.data.length - 1);
    },
    // 实际渲染的开始索引（包含缓冲区）
    startIndex() {
      return Math.max(0, this.visibleStartIndex - this.bufferSize);
    },
    // 实际渲染的结束索引（包含缓冲区）
    endIndex() {
      return Math.min(this.data.length - 1, this.visibleEndIndex + this.bufferSize);
    },
    // 偏移量
    offsetY() {
      return this.getOffsetByIndex(this.startIndex);
    },
    // 可见数据
    visibleData() {
      const result = [];
      for (let i = this.startIndex; i <= this.endIndex; i++) {
        if (this.data[i]) {
          result.push({
            index: i,
            data: this.data[i],
          });
        }
      }
      return result;
    },
    // 是否显示返回顶部按钮
    showBackToTop() {
      // 只有当数据足够多且滚动距离超过一定阈值时才显示
      return this.data.length > 10 && this.scrollTop > 200;
    },
  },
  mounted() {
    this.initResizeObserver();

    this.$nextTick(() => {
      const element = this.$refs.virtualListContentRef;
      this.containerHeight = element.offsetHeight;
    });
  },
  beforeUnmount() {
    if (this.observer) {
      this.observer.disconnect();
    }
  },
  watch: {
    containerHeight: {
      handler(newHeight) {
        if (newHeight > 0) {
          this.$nextTick(() => {
            // this.debugVirtualList();
          });
        }
      },
      immediate: true,
    },
    data: {
      handler(newData) {
        // 数据变化时清除高度缓存
        this.itemHeights.clear();
        this.$nextTick(() => {
          // this.debugVirtualList();
        });
      },
      immediate: true,
    },
  },
  methods: {
    onScroll(e) {
      this.scrollTop = e.target.scrollTop;
      this.isScrolling = true;

      // 阻止事件冒泡
      e.stopPropagation();

      // 清除滚动状态
      clearTimeout(this.scrollTimer);
      this.scrollTimer = setTimeout(() => {
        this.isScrolling = false;
      }, 150);
    },

    // 获取指定索引的item高度
    getItemHeight(index) {
      if (this.itemHeights.has(index)) {
        return this.itemHeights.get(index);
      }
      return this.itemHeight;
    },

    // 根据偏移量获取索引
    getIndexByOffset(offset) {
      let currentOffset = 0;
      for (let i = 0; i < this.data.length; i++) {
        const height = this.getItemHeight(i);
        if (currentOffset + height > offset) {
          return i;
        }
        currentOffset += height;
      }
      return this.data.length - 1;
    },

    // 根据索引获取偏移量
    getOffsetByIndex(index) {
      let offset = 0;
      for (let i = 0; i < index; i++) {
        offset += this.getItemHeight(i);
      }
      return offset;
    },

    // 设置item引用
    setItemRef(el, index) {
      if (el && this.observer) {
        // 使用ResizeObserver监听高度变化
        this.observer.observe(el);
      }
    },

    // 初始化ResizeObserver
    initResizeObserver() {
      if (window.ResizeObserver) {
        this.observer = new ResizeObserver((entries) => {
          entries.forEach((entry) => {
            const index = parseInt(entry.target.dataset.index);
            const height = entry.contentRect.height;
            if (this.itemHeights.get(index) !== height) {
              this.itemHeights.set(index, height);
            }
          });
        });
      }
    },

    // 滚动到指定索引
    scrollToIndex(index) {
      const scrollTop = this.getOffsetByIndex(index);
      this.$refs.container.scrollTop = scrollTop;
    },

    scrollToItem(item) {
      const index = this.data.findIndex((v) => v === item);
      console.log("scrollToItem index: ", index, item, this.data);
      this.scrollToIndex(index);
    },
    // 滚动到顶部
    scrollToTop() {
      if (this.$refs.container) {
        this.$refs.container.scrollTo({
          top: 0,
          behavior: "smooth",
        });
      }
    },
    // 滚动到底部
    scrollToBottom() {
      if (this.$refs.container) {
        this.$refs.container.scrollTo({
          top: this.totalHeight,
          behavior: "smooth",
        });
      }
    },
    // 调试虚拟列表
    debugVirtualList() {
      if (this.$refs.container) {
        console.log("虚拟列表状态:", {
          containerHeight: this.containerHeight,
          totalHeight: this.totalHeight,
          dataLength: this.data.length,
          visibleDataLength: this.visibleData.length,
          itemHeightsSize: this.itemHeights.size,
        });
      }
    },
    // 处理鼠标滚轮事件
    onWheel(e) {
      const container = this.$refs.container;
      if (!container) return;

      const { scrollTop, scrollHeight, clientHeight } = container;
      const deltaY = e.deltaY;

      // 检查是否可以滚动
      const canScrollUp = scrollTop > 0;
      const canScrollDown = scrollTop < scrollHeight - clientHeight - 1; // 添加1px容差

      // 如果到达边界，阻止默认行为
      if ((deltaY < 0 && !canScrollUp) || (deltaY > 0 && !canScrollDown)) {
        e.preventDefault();
        e.stopPropagation();
        return;
      }

      // 如果虚拟列表可以滚动，阻止事件冒泡到页面
      if (scrollHeight > clientHeight) {
        e.stopPropagation();
      }
    },
    // 处理触摸开始
    onTouchStart(e) {
      this.touchStartY = e.touches[0].clientY;
      this.touchStartScrollTop = this.$refs.container?.scrollTop || 0;
    },
    // 处理触摸移动
    onTouchMove(e) {
      const container = this.$refs.container;
      if (!container) return;

      const { scrollTop, scrollHeight, clientHeight } = container;
      const touchY = e.touches[0].clientY;
      const deltaY = this.touchStartY - touchY;

      // 检查是否可以滚动
      const canScrollUp = scrollTop > 0;
      const canScrollDown = scrollTop < scrollHeight - clientHeight - 1;

      // 如果到达边界，阻止默认行为
      if ((deltaY < 0 && !canScrollUp) || (deltaY > 0 && !canScrollDown)) {
        e.preventDefault();
        e.stopPropagation();
        return;
      }

      // 如果虚拟列表可以滚动，阻止事件冒泡到页面
      if (scrollHeight > clientHeight) {
        e.stopPropagation();
      }
    },
  },
};
</script>

<style scoped>
.virtual-list-container {
  position: relative;
  overflow-y: auto;
  overflow-x: hidden;
  height: 100%;
}

.virtual-list-phantom {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  z-index: -1;
}

.virtual-list-content {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
}

.virtual-list-item {
  width: 100%;
}

.back-to-top-btn {
  position: fixed;
  bottom: 130px;
  right: 24px;
  width: 56px;
  height: 56px;
  background-color: #436bff;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  box-shadow: 0 3px 5px -1px rgba(67, 107, 255, 0.2),
    0 6px 10px 0 rgba(67, 107, 255, 0.14), 0 1px 18px 0 rgba(67, 107, 255, 0.12);
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
  z-index: 1000;
  color: #fff;
  border: none;
  outline: none;
  user-select: none;
  -webkit-user-select: none;
  -webkit-tap-highlight-color: transparent;
}

.back-to-top-btn:hover {
  background-color: #3a5fd9;
  box-shadow: 0 5px 5px -3px rgba(67, 107, 255, 0.2),
    0 8px 10px 1px rgba(67, 107, 255, 0.14), 0 3px 14px 2px rgba(67, 107, 255, 0.12);
  transform: translateY(-1px);
}

.back-to-top-btn:active {
  background-color: #2f4cc7;
  box-shadow: 0 1px 3px 0 rgba(67, 107, 255, 0.2), 0 1px 1px 0 rgba(67, 107, 255, 0.14),
    0 2px 1px -1px rgba(67, 107, 255, 0.12);
  transform: translateY(0);
}

.back-to-top-btn .van-icon {
  font-size: 24px;
  color: #fff;
  transition: transform 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}

/* 淡入淡出和缩放动画 */
.fade-scale-enter-active,
.fade-scale-leave-active {
  transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}

.fade-scale-enter-from {
  opacity: 0;
  transform: scale(0.8) translateY(20px);
}

.fade-scale-leave-to {
  opacity: 0;
  transform: scale(0.8) translateY(20px);
}

.fade-scale-enter-to,
.fade-scale-leave-from {
  opacity: 1;
  transform: scale(1) translateY(0);
}
</style>
